use crate::model::sys_user::SysUser;
use bcrypt::{hash, verify, DEFAULT_COST};
use crypto::digest::Digest;
use crypto::sha2::Sha512;
use serde::{Deserialize, Serialize};
use sqlx::{Pool, Postgres};
use uuid::Uuid;
use void_log::{log_info, log_warn};

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LoginPath {
    pub username: String,
    password: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Token {
    msg: Option<String>,
    pub userid: Option<Uuid>,
    pub token: Option<String>,
    timestamp: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CheckResult<D> {
    Success(D),
    UserNotFound(D),
    PasswordError(D),
    Failure(D),
}

impl LoginPath {
    pub async fn login(&self, pool: &Pool<Postgres>) -> CheckResult<Token> {
        // 数据库查询用户
        if let Ok(sys_user) = SysUser::select_account_code(pool, &self.username).await {
            // 获取数据库密码
            let hash_password = sys_user.password.unwrap();
            // 校验传入密码
            if verify(&self.password, &hash_password).unwrap_or_default() {
                // 成功后返回Token
                log_info!("登录成功！");
                CheckResult::Success(Token::new(&sys_user.id.unwrap(), &hash_password).token_to_bcrypt())
            } else {
                log_warn!("密码错误！");
                CheckResult::PasswordError(Token::err_msg("密码错误"))
            }
        } else {
            log_warn!("用户名不存在！");
            CheckResult::UserNotFound(Token::err_msg("用户名不存在"))
        }
    }
}

impl Token {
    fn new(id: &Uuid, hash_password: &str) -> Self {
        // 获取当前时间戳
        let timestamp = chrono::Local::now().timestamp();

        // 按id, password, 时间戳的序列组合
        let mut token = String::from(id.to_string());
        token.push('&');
        token.push_str(hash_password);
        token.push('&');
        token.push_str(&timestamp.to_string());

        // sha256加密
        let mut hasher = Sha512::new();
        hasher.input_str(&token);
        let token = hasher.result_str();
        Self {
            msg: None,
            userid: Option::from(*id),
            token: Some(token),
            timestamp: Some(timestamp),
        }
    }

    pub fn token_to_bcrypt(self) -> Self {
        Self {
            msg: None,
            userid: self.userid,
            token: Option::from(hash(self.token.unwrap(), DEFAULT_COST).unwrap()),
            timestamp: self.timestamp,
        }
    }

    fn err_msg(msg: &str) -> Self {
        Self {
            msg: Some(msg.to_string()),
            ..Default::default()
        }
    }
}

impl<D> CheckResult<D> {
    pub fn new(self) -> D {
        match self {
            CheckResult::Success(d) => { d }
            CheckResult::UserNotFound(d) => { d }
            CheckResult::PasswordError(d) => { d }
            CheckResult::Failure(d) => { d }
        }
    }
}